// https://sbcode.net/threejs/physics-cannonjs/
import { 
  AxesHelper,
  BoxGeometry,
  BufferGeometry,
  Clock,
  IcosahedronGeometry,
  Mesh,
  MeshNormalMaterial,
  MeshPhongMaterial,
  PCFShadowMap,
  PerspectiveCamera,
  PlaneGeometry,
  Scene,
  SphereGeometry,
  SpotLight, 
  TorusKnotGeometry, 
  WebGLRenderer
} from "three";
import * as CANNON from 'cannon';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import stats from '../../utils/stats';
import { GUI } from 'dat.gui'

const scene = new Scene();

const renderer = new WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = PCFShadowMap;
document.body.appendChild(renderer.domElement);

const camera = new PerspectiveCamera(45, window.innerWidth/window.innerHeight, 0.1, 1000);
camera.position.set(10, 30, 40);

const light1 = new SpotLight();
light1.position.set(2.5, 5, 5);
light1.angle = Math.PI / 4;
light1.penumbra = 0.5;
light1.castShadow = true;
light1.shadow.mapSize.width = 1024;
light1.shadow.mapSize.height = 1024;
light1.shadow.camera.near = 0.5;
light1.shadow.camera.far = 20;
scene.add(light1);

const axesHelper = new AxesHelper(5);
scene.add(axesHelper);

// 该方法初始化物理世界，里面包含着物理世界的相关数据（如刚体数据，世界中所受外力等等）
const world = new CANNON.World();
// 设置物理世界的重力为沿y轴向上-9.8米每二次方秒
world.gravity.set(0, -9.8, 0);

const normalMaterial = new MeshNormalMaterial();

const cubeGeometry = new BoxGeometry(1, 1, 1);
const cube = new Mesh(cubeGeometry, normalMaterial);
cube.position.set(-3, 3, 0);
cube.castShadow = true;
scene.add(cube);

// const cubeShape = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5));
// 创建一个刚体（物理世界的刚体数据）
const cubeBody = new CANNON.Body({ 
  // 刚体的质量，这里单位为kg
  mass: 1,
  // 刚体的位置，单位是米
  position: new CANNON.Vec3(cube.position.x, cube.position.y, cube.position.z), 
  // 刚体的形状
  shape: new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5)), 
  // 材质数据，里面规定了摩擦系数和弹性系数
  // material: new CANNON.Material({friction: 0.05, restitution: 0}) 
});
// cubeBody.addShape(cubeShape);
// 设置刚体的位置，效果同上 position
// cubeBody.position.x = cube.position.x;
// cubeBody.position.y = cube.position.y;
// cubeBody.position.z = cube.position.z;
world.addBody(cubeBody);

const sphereGeometry = new SphereGeometry();
const sphere = new Mesh(sphereGeometry, normalMaterial);
sphere.position.set(-1, 3, 0);
sphere.castShadow = true;
scene.add(sphere);

const shpereShape = new CANNON.Sphere(1);
const sphereBody = new CANNON.Body({ mass: 1 });
sphereBody.addShape(shpereShape);
sphereBody.position.x = sphere.position.x;
sphereBody.position.y = sphere.position.y;
sphereBody.position.z = sphere.position.z;
world.addBody(sphereBody);

const phongMaterial = new MeshPhongMaterial();
const planeGeometry = new PlaneGeometry(25, 25);
const plane = new Mesh(planeGeometry, phongMaterial);
plane.rotateX(-Math.PI / 2);
plane.receiveShadow = true;
scene.add(plane);
const planeShape = new CANNON.Plane();
const planeBody = new CANNON.Body({ mass: 0 });
planeBody.addShape(planeShape);
planeBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI /2);
world.addBody(planeBody);

const icosahedronGeometry = new IcosahedronGeometry(1, 0);
const icosahedron = new Mesh(icosahedronGeometry, normalMaterial);
icosahedron.position.set(1, 3, 0);
icosahedron.castShadow = true;
scene.add(icosahedron);
const icosahedronPoints = icosahedron.geometry.vertices.map(function (v) {
  return new CANNON.Vec3(v.x, v.y, v.z)
});
const icosahedronFaces = icosahedron.geometry.faces.map(function (f) {
  return [f.a, f.b, f.c]
});
// @ts-ignore
const icosahedronShape = new CANNON.ConvexPolyhedron(icosahedronPoints, icosahedronFaces);
const icosahedronBody = new CANNON.Body({ mass: 1 });
icosahedronBody.addShape(icosahedronShape);
icosahedronBody.position.x = icosahedron.position.x;
icosahedronBody.position.y = icosahedron.position.y;
icosahedronBody.position.z = icosahedron.position.z;
world.addBody(icosahedronBody);

// 圆环扭结几何体
const torusKnotGeometry = new TorusKnotGeometry();
const torusKnot = new Mesh(torusKnotGeometry, normalMaterial);
torusKnot.position.set(4, 3, 0);
torusKnot.castShadow = true;
scene.add(torusKnot);
const attrsGeom = new BufferGeometry().fromGeometry(torusKnotGeometry)
const torusKnotVertices = attrsGeom.attributes.position.array;
const torusKnotIndices = Object.keys(torusKnotVertices).map(Number);
// @ts-ignore
const torusKnotShape = new CANNON.Trimesh(torusKnotVertices, torusKnotIndices);
const torusKnotBody = new CANNON.Body({ mass: 1 });
torusKnotBody.addShape(torusKnotShape);
torusKnotBody.position.x = torusKnot.position.x;
torusKnotBody.position.y = torusKnot.position.y;
torusKnotBody.position.z = torusKnot.position.z;
world.addBody(torusKnotBody);

const gui = new GUI();
const physicsFolder = gui.addFolder('Physics');
physicsFolder.add(world.gravity, 'x', -10, 10, 0.1);
physicsFolder.add(world.gravity, 'y', -10, 10, 0.1);
physicsFolder.add(world.gravity, 'z', -10, 10, 0.1);
physicsFolder.open();

const orbitControls = new OrbitControls(camera, renderer.domElement);
orbitControls.target.y = 2;
orbitControls.screenSpacePanning = true;
const clock = new Clock();

function animate() {
  stats.update();
  orbitControls.update();

  world.step(clock.getDelta());

  let {x, y, z} = cubeBody.position
  cube.position.set(x, y, z);
  ({x, y, z} = cubeBody.quaternion);
  cube.quaternion.set(x, y, z, cubeBody.quaternion.w);

  ({x, y, z} = sphereBody.position);
  sphere.position.set(x, y, z);
  ({x, y, z} = sphereBody.quaternion);
  sphere.quaternion.set(x, y, z, sphereBody.quaternion.w);

  ({x, y, z} = icosahedronBody.position);
  icosahedron.position.set(x, y, z);
  ({x, y, z} = icosahedronBody.quaternion);
  icosahedron.quaternion.set(x, y, z, icosahedronBody.quaternion.w);

  ({x, y, z} = torusKnotBody.position);
  torusKnot.position.set(x, y, z);
  ({x, y, z} = torusKnotBody.quaternion);
  torusKnot.quaternion.set(x, y, z, torusKnotBody.quaternion.w);

  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}
animate();